package com.hdu.mockService.handler;

import com.hdu.mockService.factory.MockHandlerFactory;
import com.hdu.mockService.annotation.RandomList;
import lombok.val;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.core.ResolvableType;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;

import static com.hdu.mockService.utils.PredictionUtils.unValidParam;
import static com.hdu.mockService.utils.ReflectionUtils.getFieldsByTargetAnnotation;

public class ListMockHandler implements IMockHandler {

    private final static int MIN_LIST_SIZE = 1;
    private final static int MAX_LIST_SIZE = 50;

    private final static String STRING = "String";
    private final static String INTEGER = "Integer";
    private final static String LONG = "Long";
    private final static String DOUBLE = "Double";

    @Override
    public void partitionMock(Object mockObj) {
        List<Field> listMockFields = getFieldsByTargetAnnotation(mockObj.getClass(), RandomList.class);
        if (CollectionUtils.isEmpty(listMockFields)) {
            return;
        }
        listMockFields.forEach(field -> {
            validInput(field.getAnnotation(RandomList.class));
            val fixedSize = field.getAnnotation(RandomList.class).fixedSize();
            int minSize = field.getAnnotation(RandomList.class).minSize();
            int maxSize = field.getAnnotation(RandomList.class).maxSize();
            int listSize;
            if (fixedSize != -1) {
                listSize = fixedSize;
            } else {
                minSize = minSize == -1 ? MIN_LIST_SIZE : minSize;
                maxSize = maxSize == -1 ? MAX_LIST_SIZE : maxSize;
                listSize = (int) (Math.random() * (maxSize - minSize + 1) + minSize);
            }
            setField(mockObj, field, listSize);
        });
    }

    private void setField(Object o, Field field, int listSize) {
        field.setAccessible(true);
        val elementClass = ResolvableType.forField(field).getGeneric(0).resolve();
        val randomList = field.getAnnotation(RandomList.class);
        List<Object> resList = new ArrayList<>(listSize);
        switch (Objects.requireNonNull(elementClass).getSimpleName()) {
            case STRING:
                val stringMockHandler = MockHandlerFactory.getHandlerByType(StringMockHandler.class);
                doAdd(resList, list ->
                        {
                            for (int i = 0; i < listSize; i++) {
                                list.add(
                                        stringMockHandler.getRandomString(
                                                randomList.strFixedLen(),
                                                randomList.strMinLen(),
                                                randomList.strMaxLen(),
                                                randomList.charTypes()
                                        )
                                );
                            }
                        }
                );
                break;
            case INTEGER:
                val intMockHandler = MockHandlerFactory.getHandlerByType(IntMockHandler.class);
                doAdd(resList, list ->
                        {
                            for (int i = 0; i < listSize; i++) {
                                list.add(intMockHandler.getRandomInt(randomList.intMin(), randomList.intMax()));
                            }
                        }
                );
                break;
            case LONG:
                val longMockHandler = MockHandlerFactory.getHandlerByType(LongMockHandler.class);
                doAdd(resList, list ->
                        {
                            for (int i = 0; i < listSize; i++) {
                                list.add(longMockHandler.getRandomLong(randomList.longMin(), randomList.longMax()));
                            }
                        }
                );
                break;
            case DOUBLE:
                val doubleMockHandler = MockHandlerFactory.getHandlerByType(DoubleMockHandler.class);
                doAdd(resList, list ->
                        {
                            for (int i = 0; i < listSize; i++) {
                                list.add(doubleMockHandler.getRandomDouble(randomList.doubleMin(), randomList.doubleMax()));
                            }
                        }
                );
                break;
            default:
                break;
        }
        try {
            field.set(o, resList);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    private void doAdd(List<Object> list, Consumer<List<Object>> consumer) {
        consumer.accept(list);
    }

    private void validInput(RandomList randomList) {

        val minLen = randomList.minSize();
        val maxLen = randomList.maxSize();
        val fixedLen = randomList.fixedSize();

        unValidParam(
                fixedLen != -1 && (minLen != -1 || maxLen != -1),
                "fixedSize and minSize or maxSize can not be set at the same time"
        );

        unValidParam(
                fixedLen == -1 && minLen == -1 && maxLen == -1,
                "fixedSize or minSize or maxSize must be set"
        );

        unValidParam(
                fixedLen != -1 && fixedLen < 0,
                "fixedSize must be greater than 0"
        );

        unValidParam(
                minLen != -1 && minLen < 0,
                "minSize must be greater than 0"
        );

        unValidParam(
                maxLen != -1 && maxLen < 0,
                "maxSize must be greater than 0"
        );

    }
}
